package com.competition.web.admin.service.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.aliyun.oss.model.CompleteMultipartUploadResult;
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PartETag;
import com.aliyun.oss.model.PolicyConditions;
import com.aliyun.oss.model.UploadPartRequest;
import com.aliyun.oss.model.UploadPartResult;
import com.common.util.FileFormatVerifyUtil;
import com.common.util.RandomString;
import com.common.util.WithdrawalOfSurplus;
import com.competition.web.admin.config.OssConfig;
import com.competition.web.admin.dto.OssCallbackParam;
import com.competition.web.admin.dto.OssCallbackResult;
import com.competition.web.admin.dto.OssPolicyResult;
import com.competition.web.admin.service.OssService;
import com.msg.ResponseCode;
import com.returns.value.CodeAndData;
import com.returns.value.ICodeAndData;

import cn.hutool.json.JSONUtil;

@Service
public class OssServiceImpl implements OssService {

	private static final Logger LOGGER = LoggerFactory.getLogger(OssServiceImpl.class);
	@Value("${aliyun.oss.policy.expire}")
	private int ALIYUN_OSS_EXPIRE;
	@Value("${aliyun.oss.maxSize}")
	private int ALIYUN_OSS_MAX_SIZE;
	@Value("${aliyun.oss.callback}")
	private String ALIYUN_OSS_CALLBACK;
	@Value("${aliyun.oss.bucketName}")
	private String ALIYUN_OSS_BUCKET_NAME;
	@Value("${aliyun.oss.endpoint}")
	private String ALIYUN_OSS_ENDPOINT;
	@Value("${aliyun.oss.dir.prefix}")
	private String ALIYUN_OSS_DIR_PREFIX;

	@Autowired
	private OssConfig ossConfig;

	private static Map<String, String> IMAGE_TYPE = new HashMap<>();

	static {
		IMAGE_TYPE.put(".jpg", ".jpg");
		IMAGE_TYPE.put(".png", ".png");
		IMAGE_TYPE.put(".jpeg", ".jpeg");
	}

	private OSSClient getOssClient() {
		return new OSSClient(ossConfig.getEndpoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
	}

	public void init() {

	}

	/**
	 * 签名生成
	 */
	@Override
	public OssPolicyResult policy() {
		OssPolicyResult result = new OssPolicyResult();
		// 存储目录
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
		String dir = ALIYUN_OSS_DIR_PREFIX + sdf.format(new Date());
		// 签名有效期
		long expireEndTime = System.currentTimeMillis() + ALIYUN_OSS_EXPIRE * 1000;
		Date expiration = new Date(expireEndTime);
		// 文件大小
		long maxSize = ALIYUN_OSS_MAX_SIZE * 1024 * 1024;
		// 回调
		OssCallbackParam callback = new OssCallbackParam();
		callback.setCallbackUrl(ALIYUN_OSS_CALLBACK);
		callback.setCallbackBody(
				"filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
		callback.setCallbackBodyType("application/x-www-form-urlencoded");
		// 提交节点
		String action = "http://" + ALIYUN_OSS_BUCKET_NAME + "." + ALIYUN_OSS_ENDPOINT;

		OSSClient ossClient = getOssClient();
		try {
			PolicyConditions policyConds = new PolicyConditions();
			policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize);
			policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

			String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
			byte[] binaryData = postPolicy.getBytes("utf-8");
			String policy = BinaryUtil.toBase64String(binaryData);
			String signature = ossClient.calculatePostSignature(postPolicy);
			String callbackData = BinaryUtil.toBase64String(JSONUtil.parse(callback).toString().getBytes("utf-8"));
			// 返回结果
			result.setAccessKeyId(ossClient.getCredentialsProvider().getCredentials().getAccessKeyId());
			result.setPolicy(policy);
			result.setSignature(signature);
			result.setDir(dir);
			result.setCallback(callbackData);
			result.setHost(action);
		} catch (Exception e) {
			LOGGER.error("签名生成失败", e);
		} finally {
			ossClient.shutdown();
		}
		return result;
	}

	@Override
	public OssCallbackResult callback(HttpServletRequest request) {
		OssCallbackResult result = new OssCallbackResult();
		String filename = request.getParameter("filename");
		filename = "http://".concat(ALIYUN_OSS_BUCKET_NAME).concat(".").concat(ALIYUN_OSS_ENDPOINT).concat("/")
				.concat(filename);
		result.setFilename(filename);
		result.setSize(request.getParameter("size"));
		result.setMimeType(request.getParameter("mimeType"));
		result.setWidth(request.getParameter("width"));
		result.setHeight(request.getParameter("height"));
		return result;
	}

	@Override
	public <V> ICodeAndData<String> uploadFileNFT(Function<V, String> function, MultipartFile file) {

		InputStream inputStream = null;

		OSSClient ossClient = getOssClient();

		try {

			// 获取文件名称
			String fileName = file.getOriginalFilename();

			String suffix = fileName.substring(fileName.indexOf('.'));

			if (null == IMAGE_TYPE.get(suffix)) {
				return new CodeAndData<String>(ResponseCode.ERROR_FILE_TYPE, null);
			}
			

			// 创建OSS实例。
			// 获取上传文件的输入流
			inputStream = file.getInputStream();
			
			boolean pass = FileFormatVerifyUtil.suffixVerify(inputStream, suffix);
//			
			if (false == pass) {
				return new CodeAndData<String>(ResponseCode.ERROR_FILE_TYPE, null);
			}
			


			String bucketName = this.ossConfig.getBucketName();

			SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

			Date date = new Date();

			String perfix = function.apply((V) ossConfig);

			String dir = perfix + sdf.format(date);

			RandomString randomString = new RandomString(5);

			suffix = fileName.substring(fileName.indexOf('.'));

			fileName = fileName + randomString.getString() + suffix;

			dir = dir + "/" + fileName;

			long totalSize = file.getSize();

			Field pathField = inputStream.getClass().getDeclaredField("path");

			pathField.setAccessible(true);

			String path = (String) pathField.get(inputStream);

			File cacheFile = new File(path);

			long partSize = this.ossConfig.getSize();

			// 创建InitiateMultipartUploadRequest对象。
			InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, dir);

			// 初始化分片。
			InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);

			String uploadId = upresult.getUploadId();

			// 总共分多少个
			long count = WithdrawalOfSurplus.ofSurplus(totalSize, partSize);

			long startPos = 0, curPartSize = 0;

			UploadPartRequest uploadPartRequest;

			// partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
			List<PartETag> partETags = new ArrayList<PartETag>();

			for (int i = 0; i < count; i++) {

				startPos = i * partSize;
				
				inputStream = new FileInputStream(cacheFile);

				curPartSize = (i + 1 == count) ? (totalSize - startPos) : partSize;

				// 跳过已经上传的分片。
				inputStream.skip(startPos);
				uploadPartRequest = new UploadPartRequest();
				uploadPartRequest.setBucketName(bucketName);
				uploadPartRequest.setKey(dir);
				uploadPartRequest.setUploadId(uploadId);
				uploadPartRequest.setInputStream(inputStream);
				// 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
				uploadPartRequest.setPartSize(curPartSize);
				// 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出此范围，OSS将返回InvalidArgument错误码。
				uploadPartRequest.setPartNumber(i + 1);
				// 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
				UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
				// 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
				partETags.add(uploadPartResult.getPartETag());

			}
			// 创建CompleteMultipartUploadRequest对象。
			// 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
			CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(
					bucketName, dir, uploadId, partETags);

			// 如果需要在完成分片上传的同时设置文件访问权限，请参考以下示例代码。
			// completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.Private);
			// 指定是否列举当前UploadId已上传的所有Part。如果通过服务端List分片数据来合并完整文件时，以上CompleteMultipartUploadRequest中的partETags可为null。
			// Map<String, String> headers = new HashMap<String, String>();
			// 如果指定了x-oss-complete-all:yes，则OSS会列举当前UploadId已上传的所有Part，然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
			// 如果指定了x-oss-complete-all:yes，则不允许继续指定body，否则报错。
			// headers.put("x-oss-complete-all","yes");
			// completeMultipartUploadRequest.setHeaders(headers);

			// 完成分片上传。
			CompleteMultipartUploadResult completeMultipartUploadResult = ossClient
					.completeMultipartUpload(completeMultipartUploadRequest);
			System.out.println(completeMultipartUploadResult.getETag());

			// 调用oss实例中的方法实现上传
			// 参数1： Bucket名称
			// 参数2： 上传到oss文件路径和文件名称 /aa/bb/1.jpg
			// 参数3： 上传文件的输入流
//            ossClient.putObject(ALIYUN_OSS_BUCKET_NAME, dir, inputStream);

			// 把上传后文件路径返回
			// 需要把上传到阿里云oss路径手动拼接出来
			// https://achang-edu.oss-cn-hangzhou.aliyuncs.com/default.gif
			String url = "https://" + ALIYUN_OSS_BUCKET_NAME + "." + ALIYUN_OSS_ENDPOINT + "/" + dir;
			return new CodeAndData<String>(ResponseCode.SUCCESS, url);
		} catch (Exception e) {

			LOGGER.info(e.getMessage(), e);
			return new CodeAndData<String>(ResponseCode.ERROR, e.getMessage());
		} finally {
			// 关闭OSSClient。
			ossClient.shutdown();
		}
	}

	@Override
	public Integer deleteFileToOSS(List<String> fileItems) {
		if (0 == fileItems.size()) {
			return 1;
		}

		OSSClient obsClient = getOssClient();

		String bucketName = ossConfig.getBucketName();

		String res;

		String replace = "https://" + ossConfig.getBucketName() + "." + ossConfig.getEndpoint() + "/";

		for (String string : fileItems) {

			res = string.replace(replace, "");

			obsClient.deleteObject(bucketName, res);
		}
		
		obsClient.shutdown();
		return 1;
	}

}
